https://www.tidytextmining.com/

load libraries

library(rtweet)
library(tidyverse)
library(tidytext)
library(wordcloud2)

Get Tweets

search_tweets

tweet_collection <- tweet_collection %>% 
  filter(is_retweet == "FALSE") %>% 
  filter(!str_detect(text, "electionnight")) %>% 
  filter(!str_detect(hashtags, "electionnight"))
argument is not an atomic vector; coercing
tweet_collection

Tokenize tweets

tweets_by_tweeter <- tweet_collection %>% 
  group_by(screen_name) %>% 
  mutate(line = row_number()) %>% 
  ungroup()

tweets_by_tweeter %>% 
  count(screen_name, sort = TRUE)

# glimpse(tweets_by_tweeter)
bad_hashtags <- tweets_by_tweeter %>% 
  select(status_id, hashtags) %>% 
  unnest(hashtags) %>% 
  filter(str_detect(hashtags, regex("electionnight", ignore_case = TRUE)) |
           # str_detect(hashtags, regex("election20", ignore_case = TRUE)) |
           str_detect(hashtags, "2020")) %>% 
  distinct(status_id)
bad_hashtags
  # group_by(hashtags) %>% 
  # summarise(tot_status_id = n()) %>% 
  # arrange(-tot_status_id)          # ElectionNight  / Election2020
  # count(hashtags, sort = TRUE)

tweets_by_tweeter <- tweets_by_tweeter %>% 
  anti_join(bad_hashtags)
Joining, by = "status_id"
tweets_by_tweeter

"Because we have kept text such as hashtags and usernames in the dataset, we can’t use a simple anti_join() to remove stop words. Instead, we can take the approach shown in the filter() line that uses str_detect() from the stringr package. – https://www.tidytextmining.com/twitter.html

tweets_tokenized <- tweets_by_tweeter %>% 
  select(text, screen_name, line) %>% 
  unnest_tokens(word, text, token = "tweets") %>%
  filter(!word %in% stop_words$word,
         !word %in% str_remove_all(stop_words$word, "'"),
         str_detect(word, "[a-z]")) 
Using `to_lower = TRUE` with `token = 'tweets'` may not preserve URLs.
tweets_tokenized

stop words

head(stopwordslangs)
tweets_tokenized %>% 
  count(word, sort = TRUE, name = "freq") %>% 
  filter(!str_detect(word, "^\\@")) %>% 
  anti_join(stopwordslangs)           # anti_join(tidytext::get_stopwords())
Joining, by = "word"

Word frequencies

Calculate word frequency

frequency <- tweets_tokenized %>% 
  group_by(screen_name) %>% 
  count(word, sort = TRUE) %>% 
  left_join(tweets_tokenized %>% 
              group_by(screen_name) %>% 
              summarise(total = n())) %>%
  mutate(freq = n/total)
`summarise()` ungrouping output (override with `.groups` argument)
Joining, by = "screen_name"
frequency

"This is a nice and tidy data frame but we would actually like to plot those frequencies on the x- and y-axes of a plot, so we will need to use spread() from tidyr make a differently shaped data frame. – https://www.tidytextmining.com/twitter.html

pivot_wider

frequency <- frequency %>% 
  select(screen_name, word, freq) %>% 
  pivot_wider(names_from = screen_name, values_from = freq) #, values_fill = 0)

frequency 

viz it

tweets_tokenized %>% 
  # group_by(screen_name) %>% 
  count(word, sort = TRUE, name = "freq") %>% 
  head(200) %>% 
  filter(!str_detect(word, "^\\@")) %>% 
  anti_join(stopwordslangs)  %>% 
  wordcloud2(size = 2)
Joining, by = "word"
tweets_tokenized %>% 
  count(word, sort = TRUE, name = "freq") %>% 
  filter(!str_detect(word, "^\\@")) %>% 
  slice_head(n = 30) %>% 
  ggplot(aes(freq, fct_reorder(word, freq))) +
  geom_col()


tweets_tokenized %>% 
  count(word, sort = TRUE, name = "freq") %>% 
  anti_join(stopwordslangs) %>% 
  filter(!str_detect(word, "^\\@")) %>% 
  slice_head(n = 30) %>% 
  ggplot(aes(freq, fct_reorder(word, freq))) +
  geom_col()
Joining, by = "word"

ggplot(frequency, aes(SakuraYamazaki5, HinaYamazaki1)) +
  geom_jitter(alpha = 0.1, size = 2.5, width = 0.25, height = 0.25) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) +
  scale_x_log10(labels = scales::percent_format()) +
  scale_y_log10(labels = scales::percent_format()) +
  geom_abline(color = "firebrick")

Word Usage

tweets_by_tweeter %>% 
  summarise(min_date = min(created_at), max_date = max(created_at))
word_ratios <- tweets_tokenized %>%
  # filter(screen_name == "CBBCent1" | screen_name == "Adam_Bradford14") %>%
  filter(screen_name == "SakuraYamazaki5" | screen_name == "HinaYamazaki1") %>% 
  filter(!str_detect(word, "^@")) %>%
  count(word, screen_name) %>%
  group_by(word) %>%
  filter(sum(n) >= 2) %>%
  ungroup() %>%
  pivot_wider(names_from = screen_name, values_from = n, values_fill = 0) %>%
  mutate_if(is.numeric, list(~(. + 1) / (sum(.) + 1))) %>%
  mutate(logratio = log(SakuraYamazaki5  / HinaYamazaki1)) %>%
  arrange(desc(logratio))

word_ratios

equal usage

word_ratios %>% 
  arrange(abs(logratio))
word_ratios %>%
  group_by(logratio < 0) %>%
  top_n(15, abs(logratio)) %>%
  ungroup() %>%
  mutate(word = reorder(word, logratio)) %>%
  ggplot(aes(word, logratio, fill = logratio < 0)) +
  geom_col() + #show.legend = FALSE) +
  coord_flip() +
  ylab("log odds ratio (SakuraYamazaki5/HinaYamazaki1)") +
  scale_fill_discrete(name = "", labels = c("SakuraYamazaki5", "HinaYamazaki1"))

Favorites and retweets

https://www.tidytextmining.com/twitter.html#favorites-and-retweets

Changes in word use

https://www.tidytextmining.com/twitter.html#changes-in-word-use

Term Document Matrix

{r}
# dtm <- DocumentTermMatrix(docs) 

dtm2 <- TermDocumentMatrix(corpus)
m <- as.matrix(dtm2)
v <- sort(rowSums(m),decreasing=TRUE)
d <- data.frame(word = names(v),freq=v)

d <- d %>% 
  slice(2:200)

https://www.tidytextmining.com/tfidf.html#the-bind_tf_idf-function

tweet_words <- tweets_by_tweeter %>% 
  slice_head(n = 50) %>% 
  select(screen_name, text, status_id, user_id) %>%  
  unnest_tokens(word, text, token = "tweets") %>% 
  filter(!str_detect(word, "^\\@")) %>%
  filter(!str_detect(word, "^http")) %>%
  anti_join(stopwordslangs)  %>% 
  count(word, tweeter = screen_name, sort = TRUE)
Using `to_lower = TRUE` with `token = 'tweets'` may not preserve URLs.
Joining, by = "word"
tweet_words

total_words <- tweet_words %>%
  group_by(tweeter) %>%
  summarize(total = sum(n)) %>% 
  arrange(-total)
`summarise()` ungrouping output (override with `.groups` argument)
total_words

tweet_words <- left_join(tweet_words, total_words)
Joining, by = "tweeter"
tweet_words
tweet_words %>% 
  bind_tf_idf(word, tweeter, n)
tweet_words %>% 
  # filter(tweeter != "cpsbeingweird") %>% 
  bind_tf_idf(word, tweeter, n) %>% 
  arrange(desc(tf_idf)) %>% 
  mutate(word = factor(word, levels = rev(unique(word)))) %>% 
  filter(n > 2) %>% 
  group_by(tweeter) %>%
  # top_n(10) %>%
  # ungroup() %>% 
  ggplot(aes(word, tf_idf)) +
  geom_col() +
  facet_wrap(~ tweeter) +
  coord_flip()

Resource list

LS0tDQp0aXRsZTogIlJ0d2VldCINCnN1YnRpdGxlOiAiYW4gUmZ1biBkZW1vbnN0cmF0aW9uIg0KYXV0aG9yOiAiSm9obiBMaXR0bGUiDQpkYXRlOiAnYHIgU3lzLkRhdGUoKWAnDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vDQoNCiMjIGxvYWQgbGlicmFyaWVzDQoNCmBgYHtyIGxpYnJhcmllcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocnR3ZWV0KQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KbGlicmFyeSh3b3JkY2xvdWQyKQ0KYGBgDQoNCg0KDQoNCiMjIEdldCBUd2VldHMgDQoNCnNlYXJjaF90d2VldHMNCmBgYHtyIGdldFR3ZWV0c30NCnR3ZWV0X2NvbGxlY3Rpb24gPC0gc2VhcmNoX3R3ZWV0cygiI2Ftb25ndXNmYW5hcnQiLCBuPTQwMDAsIGxhbmcgPSAiZW4iKQ0KDQp0d2VldF9jb2xsZWN0aW9uLm9yaWcgPC0gdHdlZXRfY29sbGVjdGlvbg0KYGBgDQoNCg0KYGBge3IgcHJvY2VzcyBUd2VldHN9DQp0d2VldF9jb2xsZWN0aW9uIDwtIHR3ZWV0X2NvbGxlY3Rpb24gJT4lIA0KICBmaWx0ZXIoaXNfcmV0d2VldCA9PSAiRkFMU0UiKSAlPiUgDQogIGZpbHRlcighc3RyX2RldGVjdCh0ZXh0LCAiZWxlY3Rpb25uaWdodCIpKSAlPiUgDQogIGZpbHRlcighc3RyX2RldGVjdChoYXNodGFncywgImVsZWN0aW9ubmlnaHQiKSkNCg0KdHdlZXRfY29sbGVjdGlvbg0KYGBgDQoNCg0KIyMgVG9rZW5pemUgdHdlZXRzDQoNCmBgYHtyIGNvcnB1czJ2ZWN0b3J9DQp0d2VldHNfYnlfdHdlZXRlciA8LSB0d2VldF9jb2xsZWN0aW9uICU+JSANCiAgZ3JvdXBfYnkoc2NyZWVuX25hbWUpICU+JSANCiAgbXV0YXRlKGxpbmUgPSByb3dfbnVtYmVyKCkpICU+JSANCiAgdW5ncm91cCgpDQoNCnR3ZWV0c19ieV90d2VldGVyICU+JSANCiAgY291bnQoc2NyZWVuX25hbWUsIHNvcnQgPSBUUlVFKQ0KDQojIGdsaW1wc2UodHdlZXRzX2J5X3R3ZWV0ZXIpDQpgYGANCg0KYGBge3J9DQpiYWRfaGFzaHRhZ3MgPC0gdHdlZXRzX2J5X3R3ZWV0ZXIgJT4lIA0KICBzZWxlY3Qoc3RhdHVzX2lkLCBoYXNodGFncykgJT4lIA0KICB1bm5lc3QoaGFzaHRhZ3MpICU+JSANCiAgZmlsdGVyKHN0cl9kZXRlY3QoaGFzaHRhZ3MsIHJlZ2V4KCJlbGVjdGlvbm5pZ2h0IiwgaWdub3JlX2Nhc2UgPSBUUlVFKSkgfA0KICAgICAgICAgICAjIHN0cl9kZXRlY3QoaGFzaHRhZ3MsIHJlZ2V4KCJlbGVjdGlvbjIwIiwgaWdub3JlX2Nhc2UgPSBUUlVFKSkgfA0KICAgICAgICAgICBzdHJfZGV0ZWN0KGhhc2h0YWdzLCAiMjAyMCIpKSAlPiUgDQogIGRpc3RpbmN0KHN0YXR1c19pZCkNCmJhZF9oYXNodGFncw0KICAjIGdyb3VwX2J5KGhhc2h0YWdzKSAlPiUgDQogICMgc3VtbWFyaXNlKHRvdF9zdGF0dXNfaWQgPSBuKCkpICU+JSANCiAgIyBhcnJhbmdlKC10b3Rfc3RhdHVzX2lkKSAgICAgICAgICAjIEVsZWN0aW9uTmlnaHQgIC8gRWxlY3Rpb24yMDIwDQogICMgY291bnQoaGFzaHRhZ3MsIHNvcnQgPSBUUlVFKQ0KDQp0d2VldHNfYnlfdHdlZXRlciA8LSB0d2VldHNfYnlfdHdlZXRlciAlPiUgDQogIGFudGlfam9pbihiYWRfaGFzaHRhZ3MpDQoNCnR3ZWV0c19ieV90d2VldGVyDQpgYGANCg0KDQoNCj4gIkJlY2F1c2Ugd2UgaGF2ZSBrZXB0IHRleHQgc3VjaCBhcyBoYXNodGFncyBhbmQgdXNlcm5hbWVzIGluIHRoZSBkYXRhc2V0LCB3ZSBjYW7igJl0IHVzZSBhIHNpbXBsZSBhbnRpX2pvaW4oKSB0byByZW1vdmUgc3RvcCB3b3Jkcy4gSW5zdGVhZCwgd2UgY2FuIHRha2UgdGhlIGFwcHJvYWNoIHNob3duIGluIHRoZSBmaWx0ZXIoKSBsaW5lIHRoYXQgdXNlcyBzdHJfZGV0ZWN0KCkgZnJvbSB0aGUgc3RyaW5nciBwYWNrYWdlLiAtLSBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vdHdpdHRlci5odG1sDQoNCg0KYGBge3IgdG9rZW5pemVkIHR3ZWV0c30NCnR3ZWV0c190b2tlbml6ZWQgPC0gdHdlZXRzX2J5X3R3ZWV0ZXIgJT4lIA0KICBzZWxlY3QodGV4dCwgc2NyZWVuX25hbWUsIGxpbmUpICU+JSANCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0LCB0b2tlbiA9ICJ0d2VldHMiKSAlPiUNCiAgZmlsdGVyKCF3b3JkICVpbiUgc3RvcF93b3JkcyR3b3JkLA0KICAgICAgICAgIXdvcmQgJWluJSBzdHJfcmVtb3ZlX2FsbChzdG9wX3dvcmRzJHdvcmQsICInIiksDQogICAgICAgICBzdHJfZGV0ZWN0KHdvcmQsICJbYS16XSIpKSANCg0KdHdlZXRzX3Rva2VuaXplZA0KYGBgDQoNCiMjIHN0b3Agd29yZHMNCg0KYGBge3J9DQpoZWFkKHN0b3B3b3Jkc2xhbmdzKQ0KYGBgDQoNCg0KYGBge3J9DQp0d2VldHNfdG9rZW5pemVkICU+JSANCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUsIG5hbWUgPSAiZnJlcSIpICU+JSANCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KHdvcmQsICJeXFxAIikpICU+JSANCiAgYW50aV9qb2luKHN0b3B3b3Jkc2xhbmdzKSAgICAgICAgICAgIyBhbnRpX2pvaW4odGlkeXRleHQ6OmdldF9zdG9wd29yZHMoKSkNCmBgYA0KDQoNCg0KDQojIyBXb3JkIGZyZXF1ZW5jaWVzDQoNCiMjIyBDYWxjdWxhdGUgd29yZCBmcmVxdWVuY3kNCg0KYGBge3J9DQpmcmVxdWVuY3kgPC0gdHdlZXRzX3Rva2VuaXplZCAlPiUgDQogIGdyb3VwX2J5KHNjcmVlbl9uYW1lKSAlPiUgDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUgDQogIGxlZnRfam9pbih0d2VldHNfdG9rZW5pemVkICU+JSANCiAgICAgICAgICAgICAgZ3JvdXBfYnkoc2NyZWVuX25hbWUpICU+JSANCiAgICAgICAgICAgICAgc3VtbWFyaXNlKHRvdGFsID0gbigpKSkgJT4lDQogIG11dGF0ZShmcmVxID0gbi90b3RhbCkNCg0KZnJlcXVlbmN5DQpgYGANCg0KPiAiVGhpcyBpcyBhIG5pY2UgYW5kIHRpZHkgZGF0YSBmcmFtZSBidXQgd2Ugd291bGQgYWN0dWFsbHkgbGlrZSB0byBwbG90IHRob3NlIGZyZXF1ZW5jaWVzIG9uIHRoZSB4LSBhbmQgeS1heGVzIG9mIGEgcGxvdCwgc28gd2Ugd2lsbCBuZWVkIHRvIHVzZSBzcHJlYWQoKSBmcm9tIHRpZHlyIG1ha2UgYSBkaWZmZXJlbnRseSBzaGFwZWQgZGF0YSBmcmFtZS4gLS0gaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3R3aXR0ZXIuaHRtbA0KDQpwaXZvdF93aWRlcg0KDQpgYGB7cn0NCmZyZXF1ZW5jeSA8LSBmcmVxdWVuY3kgJT4lIA0KICBzZWxlY3Qoc2NyZWVuX25hbWUsIHdvcmQsIGZyZXEpICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHNjcmVlbl9uYW1lLCB2YWx1ZXNfZnJvbSA9IGZyZXEpICMsIHZhbHVlc19maWxsID0gMCkNCg0KZnJlcXVlbmN5IA0KYGBgDQoNCiMjIyB2aXogaXQNCg0KYGBge3Igd29yZC1jbG91ZH0NCnR3ZWV0c190b2tlbml6ZWQgJT4lIA0KICAjIGdyb3VwX2J5KHNjcmVlbl9uYW1lKSAlPiUgDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFLCBuYW1lID0gImZyZXEiKSAlPiUgDQogIGhlYWQoMjAwKSAlPiUgDQogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkLCAiXlxcQCIpKSAlPiUgDQogIGFudGlfam9pbihzdG9wd29yZHNsYW5ncykgICU+JSANCiAgd29yZGNsb3VkMihzaXplID0gMikNCg0KYGBgDQpgYGB7ciB3b3JkLWZyZXEgYmFycGxvdCwgZmlnLmhlaWdodD03fQ0KdHdlZXRzX3Rva2VuaXplZCAlPiUgDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFLCBuYW1lID0gImZyZXEiKSAlPiUgDQogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkLCAiXlxcQCIpKSAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDMwKSAlPiUgDQogIGdncGxvdChhZXMoZnJlcSwgZmN0X3Jlb3JkZXIod29yZCwgZnJlcSkpKSArDQogIGdlb21fY29sKCkNCg0KdHdlZXRzX3Rva2VuaXplZCAlPiUgDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFLCBuYW1lID0gImZyZXEiKSAlPiUgDQogIGFudGlfam9pbihzdG9wd29yZHNsYW5ncykgJT4lIA0KICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgIl5cXEAiKSkgJT4lIA0KICBzbGljZV9oZWFkKG4gPSAzMCkgJT4lIA0KICBnZ3Bsb3QoYWVzKGZyZXEsIGZjdF9yZW9yZGVyKHdvcmQsIGZyZXEpKSkgKw0KICBnZW9tX2NvbCgpDQpgYGANCg0KDQpgYGB7ciB3b3JkX2ZyZXEgcGxvdH0NCmdncGxvdChmcmVxdWVuY3ksIGFlcyhTYWt1cmFZYW1hemFraTUsIEhpbmFZYW1hemFraTEpKSArDQogIGdlb21faml0dGVyKGFscGhhID0gMC4xLCBzaXplID0gMi41LCB3aWR0aCA9IDAuMjUsIGhlaWdodCA9IDAuMjUpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHdvcmQpLCBjaGVja19vdmVybGFwID0gVFJVRSwgdmp1c3QgPSAxLjUpICsNCiAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCkpICsNCiAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCkpICsNCiAgZ2VvbV9hYmxpbmUoY29sb3IgPSAiZmlyZWJyaWNrIikNCmBgYA0KDQoNCiMjIFdvcmQgVXNhZ2UNCg0KDQpgYGB7cn0NCnR3ZWV0c19ieV90d2VldGVyICU+JSANCiAgc3VtbWFyaXNlKG1pbl9kYXRlID0gbWluKGNyZWF0ZWRfYXQpLCBtYXhfZGF0ZSA9IG1heChjcmVhdGVkX2F0KSkNCmBgYA0KDQoNCmBgYHtyfQ0Kd29yZF9yYXRpb3MgPC0gdHdlZXRzX3Rva2VuaXplZCAlPiUNCiAgIyBmaWx0ZXIoc2NyZWVuX25hbWUgPT0gIkNCQkNlbnQxIiB8IHNjcmVlbl9uYW1lID09ICJBZGFtX0JyYWRmb3JkMTQiKSAlPiUNCiAgZmlsdGVyKHNjcmVlbl9uYW1lID09ICJTYWt1cmFZYW1hemFraTUiIHwgc2NyZWVuX25hbWUgPT0gIkhpbmFZYW1hemFraTEiKSAlPiUgDQogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkLCAiXkAiKSkgJT4lDQogIGNvdW50KHdvcmQsIHNjcmVlbl9uYW1lKSAlPiUNCiAgZ3JvdXBfYnkod29yZCkgJT4lDQogIGZpbHRlcihzdW0obikgPj0gMikgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHNjcmVlbl9uYW1lLCB2YWx1ZXNfZnJvbSA9IG4sIHZhbHVlc19maWxsID0gMCkgJT4lDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBsaXN0KH4oLiArIDEpIC8gKHN1bSguKSArIDEpKSkgJT4lDQogIG11dGF0ZShsb2dyYXRpbyA9IGxvZyhTYWt1cmFZYW1hemFraTUgIC8gSGluYVlhbWF6YWtpMSkpICU+JQ0KICBhcnJhbmdlKGRlc2MobG9ncmF0aW8pKQ0KDQp3b3JkX3JhdGlvcw0KYGBgDQoNCiMjIyBlcXVhbCB1c2FnZQ0KDQpgYGB7cn0NCndvcmRfcmF0aW9zICU+JSANCiAgYXJyYW5nZShhYnMobG9ncmF0aW8pKQ0KYGBgDQoNCg0KYGBge3Igd29yZC11c2FnZX0NCndvcmRfcmF0aW9zICU+JQ0KICBncm91cF9ieShsb2dyYXRpbyA8IDApICU+JQ0KICB0b3BfbigxNSwgYWJzKGxvZ3JhdGlvKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIGxvZ3JhdGlvKSkgJT4lDQogIGdncGxvdChhZXMod29yZCwgbG9ncmF0aW8sIGZpbGwgPSBsb2dyYXRpbyA8IDApKSArDQogIGdlb21fY29sKCkgKyAjc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBjb29yZF9mbGlwKCkgKw0KICB5bGFiKCJsb2cgb2RkcyByYXRpbyAoU2FrdXJhWWFtYXpha2k1L0hpbmFZYW1hemFraTEpIikgKw0KICBzY2FsZV9maWxsX2Rpc2NyZXRlKG5hbWUgPSAiIiwgbGFiZWxzID0gYygiU2FrdXJhWWFtYXpha2k1IiwgIkhpbmFZYW1hemFraTEiKSkNCmBgYA0KDQoNCiMjIEZhdm9yaXRlcyBhbmQgcmV0d2VldHMNCg0KaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3R3aXR0ZXIuaHRtbCNmYXZvcml0ZXMtYW5kLXJldHdlZXRzDQoNCiMjIENoYW5nZXMgaW4gd29yZCB1c2UNCg0KaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3R3aXR0ZXIuaHRtbCNjaGFuZ2VzLWluLXdvcmQtdXNlDQoNCiMjIFRlcm0gRG9jdW1lbnQgTWF0cml4DQoNCmBgYA0Ke3J9DQojIGR0bSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoZG9jcykgDQoNCmR0bTIgPC0gVGVybURvY3VtZW50TWF0cml4KGNvcnB1cykNCm0gPC0gYXMubWF0cml4KGR0bTIpDQp2IDwtIHNvcnQocm93U3VtcyhtKSxkZWNyZWFzaW5nPVRSVUUpDQpkIDwtIGRhdGEuZnJhbWUod29yZCA9IG5hbWVzKHYpLGZyZXE9dikNCg0KZCA8LSBkICU+JSANCiAgc2xpY2UoMjoyMDApDQoNCmBgYA0KDQpodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vdGZpZGYuaHRtbCN0aGUtYmluZF90Zl9pZGYtZnVuY3Rpb24NCg0KYGBge3J9DQp0d2VldF93b3JkcyA8LSB0d2VldHNfYnlfdHdlZXRlciAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDUwKSAlPiUgDQogIHNlbGVjdChzY3JlZW5fbmFtZSwgdGV4dCwgc3RhdHVzX2lkLCB1c2VyX2lkKSAlPiUgIA0KICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQsIHRva2VuID0gInR3ZWV0cyIpICU+JSANCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KHdvcmQsICJeXFxAIikpICU+JQ0KICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgIl5odHRwIikpICU+JQ0KICBhbnRpX2pvaW4oc3RvcHdvcmRzbGFuZ3MpICAlPiUgDQogIGNvdW50KHdvcmQsIHR3ZWV0ZXIgPSBzY3JlZW5fbmFtZSwgc29ydCA9IFRSVUUpDQoNCg0KdHdlZXRfd29yZHMNCg0KdG90YWxfd29yZHMgPC0gdHdlZXRfd29yZHMgJT4lDQogIGdyb3VwX2J5KHR3ZWV0ZXIpICU+JQ0KICBzdW1tYXJpemUodG90YWwgPSBzdW0obikpICU+JSANCiAgYXJyYW5nZSgtdG90YWwpDQoNCnRvdGFsX3dvcmRzDQoNCnR3ZWV0X3dvcmRzIDwtIGxlZnRfam9pbih0d2VldF93b3JkcywgdG90YWxfd29yZHMpDQoNCnR3ZWV0X3dvcmRzDQpgYGANCg0KYGBge3J9DQp0d2VldF93b3JkcyAlPiUgDQogIGJpbmRfdGZfaWRmKHdvcmQsIHR3ZWV0ZXIsIG4pDQpgYGANCg0KYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTE1fQ0KdHdlZXRfd29yZHMgJT4lIA0KICAjIGZpbHRlcih0d2VldGVyICE9ICJjcHNiZWluZ3dlaXJkIikgJT4lIA0KICBiaW5kX3RmX2lkZih3b3JkLCB0d2VldGVyLCBuKSAlPiUgDQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKSAlPiUgDQogIG11dGF0ZSh3b3JkID0gZmFjdG9yKHdvcmQsIGxldmVscyA9IHJldih1bmlxdWUod29yZCkpKSkgJT4lIA0KICBmaWx0ZXIobiA+IDIpICU+JSANCiAgZ3JvdXBfYnkodHdlZXRlcikgJT4lDQogICMgdG9wX24oMTApICU+JQ0KICAjIHVuZ3JvdXAoKSAlPiUgDQogIGdncGxvdChhZXMod29yZCwgdGZfaWRmKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgZmFjZXRfd3JhcCh+IHR3ZWV0ZXIpICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KDQoNCg0KDQojIyBSZXNvdXJjZSBsaXN0DQoNCi0gaHR0cDovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC93aWtpL3RleHQtbWluaW5nLWFuZC13b3JkLWNsb3VkLWZ1bmRhbWVudGFscy1pbi1yLTUtc2ltcGxlLXN0ZXBzLXlvdS1zaG91bGQta25vdw0KDQotIGh0dHA6Ly9hbnRvbmlvLWZlcnJhcm8uZXUucG4vd29yZC1jbG91ZHMtaW4tci1wYWNrYWdlcy13b3JkY2xvdWQyLWFuZC10bS8NCg0KLSBodHRwczovL2pybm9sZC5naXRodWIuaW8vcXNzLXRpZHkvZGlzY292ZXJ5Lmh0bWwjdGV4dHVhbC1kYXRhDQoNCi0gaHR0cHM6Ly9yc3R1ZGlvLXB1YnMtc3RhdGljLnMzLmFtYXpvbmF3cy5jb20vMzE4NjdfODIzNjk4N2NmMGE4NDQ0ZTk2MmNjZDJhZWM0NmQ5YzMuaHRtbA0KDQotIG9mIGxlc3MgdXNlDQoNCiAgICAtIGh0dHA6Ly93d3cuY29va2Jvb2stci5jb20vTWFuaXB1bGF0aW5nX2RhdGEvQ29udmVydGluZ19iZXR3ZWVuX2RhdGFfZnJhbWVzX2FuZF9jb250aW5nZW5jeV90YWJsZXMvDQogICAgLSBodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS9ob3ctdG8tZ2V0LXRoZS1mcmVxdWVuY3ktdGFibGUtb2YtYS1jYXRlZ29yaWNhbC12YXJpYWJsZS1hcy1hLWRhdGEtZnJhbWUtaW4tci8NCiAgICAtIGh0dHBzOi8vd3d3LnF1b3JhLmNvbS9Ib3ctZG8tSS1nZXQtYS1mcmVxdWVuY3ktY291bnQtYmFzZWQtb24tdHdvLWNvbHVtbnMtdmFyaWFibGVzLWluLWFuLVItZGF0YWZyYW1lDQogICAgLSBodHRwczovL3d3dy5xdW9yYS5jb20vSG93LWRvLXlvdS1jcmVhdGUtYS1jb3JwdXMtZnJvbS1hLWRhdGEtZnJhbWUtaW4tUg0KICAgIA0KDQo=